/*
 * SHA1.cpp
 *
 *  Created on: Jul 19, 2012
 *      Author: hooyao
 */
#define _DEBUG

#include "header/SHA1.h";
#include <string.h>
#include <assert.h>

#ifdef _DEBUG
#include <iostream>
#endif



void SHA1::update(unsigned char* message, unsigned long len) {
	this->m_msg = message;
	this->m_msglength = len;
	this->m_lenData = new unsigned char[LEN_LEN];
	memset(this->m_lenData, 0, LEN_LEN);
}

void SHA1::dofinal(void) {
	if (this->m_msg) {
		//append 1 to the end of the message，by constructing a residual
 		unsigned long rLen = (this->m_msglength + LEN_LEN + 1) % BLK_LEN;
		rLen = BLK_LEN - rLen;
		rLen = rLen == 0 ? 1 : rLen + 1;
		this->m_residual = new unsigned char[rLen];
		memset(this->m_residual, 0, rLen);
		this->m_residual[0] = 0x80;
#ifdef _DEBUG
		unsigned char reCopy[rLen];
		memcpy(reCopy, this->m_residual, rLen);
		for (unsigned int i = 0; i < rLen; ++i) {
			printf("%02X", reCopy[i]);
		}
		printf("\n");
#endif
		//convert long to 64bit big endian
		unsigned long mask = 0x00000000000000FFl;
		for (int i = 1; i <= LEN_LEN; i++) {
			this->m_lenData[i-1] = (char) ((this->m_msglength >> (64 - 8 * i))
					& mask);
		}
#ifdef _DEBUG
		unsigned char lenCopy[8];
		memcpy(lenCopy, this->m_lenData, 8);
		for (unsigned int i = 0; i < 8; ++i) {
			printf("%02X", lenCopy[i]);
		}
		printf("\n");
#endif
		//broke msg into 512bit/64byte chunks
		unsigned long position = 0;
		unsigned int h[5];
		h[0] = h0;
		h[1] = h1;
		h[2] = h2;
		h[3] = h3;
		h[4] = h4;
		while (position + BLK_LEN < this->m_msglength) {
			unsigned int * words = new unsigned int[80];
			memset(words, 0, 80);
#ifdef _DEBUG

			for (unsigned int i = 0; i < 80; ++i) {
				printf("%02X", words[i]);
			}
			printf("\n");
#endif
			//break 512bit chunk into 16 32bit big-endian which is int
			for (int i = 0; i < BLK_LEN / 4; i++) {
				for (int j = 0; j < 4; j++) {
					words[i] = words[i]
							| ((this->m_msg[position + i * 4 + j] & 0x000000FF)
									<< (4 - j));
				}
			}
			for (int i = 16; i < 80; i++) {
				unsigned int tmp = words[i - 3] ^ words[i - 8] ^ words[i - 14]
						^ words[i - 16];
				words[i] = RotateLeft(tmp, 1);
			}
			unsigned int a = h0, b = h1, c = h2, d = h3, e = h4, f = 0, k = 0;
			for (int i = 0; i < 80; i++) {
				if (i >= 0 && i <= 19) {
					f = (b & c) | ((~b) & d);
					k = 0x5A827999;
				} else if (i >= 20 && i <= 39) {
					f = b ^ c ^ d;
					k = 0x6ED9EBA1;
				} else if (i >= 40 && i <= 59) {
					f = (b & c) | (b & d) | (c & d);
					k = 0x8F1BBCDC;
				} else if (i >= 60 && i <= 79) {
					f = b ^ c ^ d;
					k = 0xCA62C1D6;
				}
				unsigned int temp = RotateLeft(a, 5);
				temp = temp + f + e + k + words[i];
				temp = temp + f + e + k + words[i];
				e = d;
				d = c;
				c = RotateLeft(b, 30);
				b = a;
				a = temp;
			}
			h[0] += a;
			h[1] += b;
			h[2] += c;
			h[3] += d;
			h[4] += e;
			position += BLK_LEN;
		}
		//proceed to last several chunks
		unsigned long leftMsgLen = this->m_msglength - position;
		unsigned int restChunkCount =
				leftMsgLen + rLen + LEN_LEN > BLK_LEN ? 2 : 1;
		unsigned char* lastChunks = new unsigned char[restChunkCount * BLK_LEN];
		memcpy(lastChunks, &this->m_msg[position],
				this->m_msglength - position);
		memcpy(&lastChunks[this->m_msglength - position ], this->m_residual,
				rLen);
		memcpy(&lastChunks[this->m_msglength - position + rLen],
				this->m_lenData, LEN_LEN);
#ifdef _DEBUG

			for (unsigned int i = 0; i < 64; ++i) {
				printf("%02X", lastChunks[i]);
			}
			printf("\n");
#endif
		unsigned long lastChunkPos = 0;
		while (lastChunkPos + BLK_LEN <= restChunkCount * BLK_LEN) {
			unsigned int * words = new unsigned int[80];
			memset(words, 0, 80);

			//break 512bit chunk into 16 32bit big-endian which is int
			for (int i = 0; i < BLK_LEN / 4; i++) {
				unsigned int cur = words[i];
				for (int j = 0; j < 4; j++) {
					 cur = cur
							| ((lastChunks[lastChunkPos + i * 4 + j] & 0x000000FF)
									<< (24 - 8*j));

				}
				words[i] = cur;
			}
#ifdef _DEBUG

			for (unsigned int i = 0; i < 16; ++i) {
				printf("%08X", words[i]);
			}
			printf("\n");
#endif
			for (int i = 16; i < 80; i++) {
				unsigned int tmp = words[i - 3] ^ words[i - 8] ^ words[i - 14]
						^ words[i - 16];
				words[i] = RotateLeft(tmp, 1);
			}
#ifdef _DEBUG

			for (unsigned int i = 0; i < 80; ++i) {
				printf("%02X", words[i]);
			}
			printf("\n");
#endif
			unsigned int a = h0, b = h1, c = h2, d = h3, e = h4, f = 0, k = 0;
			for (int i = 0; i < 80; i++) {
				if (i >= 0 && i <= 19) {
					f = (b & c) | ((~b) & d);
					k = 0x5A827999;
				} else if (i >= 20 && i <= 39) {
					f = b ^ c ^ d;
					k = 0x6ED9EBA1;
				} else if (i >= 40 && i <= 59) {
					f = (b & c) | (b & d) | (c & d);
					k = 0x8F1BBCDC;
				} else if (i >= 60 && i <= 79) {
					f = b ^ c ^ d;
					k = 0xCA62C1D6;
				}
				unsigned int temp = RotateLeft(a, 5);
				temp = temp + f + e + k + words[i];
				e = d;
				d = c;
				c = RotateLeft(b, 30);
				b = a;
				a = temp;
			}
			h[0] += a;
			h[1] += b;
			h[2] += c;
			h[3] += d;
			h[4] += e;
			lastChunkPos += BLK_LEN;
		}
		this->digest = new unsigned char[20];
		for (int i = 0; i < 5; i++) {
			for (int j = 1; j <= 4; j++) {
				this->digest[i * 4 + j-1] =
						(char) ((h[i] >> (32 - 8 * j)) & mask);
			}
		}
	} else
		throw -20;
}

